// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Author: Markus Gutschke

#ifdef USE_OPENSSL

#include <errno.h>
#include <stdlib.h>
#include <string.h>

#include "check.h"
#include "ssl.h"

struct SSLHandle {
  SSL     *ssl;
};


struct SSL *newSSL(void) {
  struct SSL *ssl;
  check(ssl = malloc(sizeof(struct SSL)));
  initSSL(ssl);
  return ssl;
}

void initSSL(struct SSL *ssl) {
  static int initialized = 0;
  if (!initialized) {
    // Global initialization
    ++initialized;
    SSL_library_init();
    SSL_load_error_strings();
  }
  SSL_METHOD *meth;
  check(meth = TLSv1_client_method());
  check(ssl->ssl_ctx = SSL_CTX_new(meth));
}

void destroySSL(struct SSL *ssl) {
  if (ssl) {
    SSL_CTX_free(ssl->ssl_ctx);
  }
}

void deleteSSL(struct SSL *ssl) {
  destroySSL(ssl);
  free(ssl);
}

struct SSLHandle *openSSLHandle(struct SSL *ssl, int fd) {
  struct SSLHandle *handle;
  check(handle      = malloc(sizeof(struct SSLHandle)));
  check(handle->ssl = SSL_new(ssl->ssl_ctx));
  BIO *bio;
  check(bio         = BIO_new_socket(fd, BIO_NOCLOSE));
  SSL_set_bio(handle->ssl, bio, bio);
  do {
    int rc = SSL_connect(handle->ssl);
    if (rc <= 0) {
      int err = SSL_get_error(handle->ssl, rc);
      check(err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE ||
            (err == SSL_ERROR_SYSCALL && errno == EINTR));
      continue;
    }
  } while (0);
  return handle;
}

void closeSSLHandle(struct SSLHandle *handle) {
  SSL_free(handle->ssl);
  free(handle);
}

ssize_t SSLread(struct SSLHandle *handle, char *buf, size_t sz) {
  errno = 0;
  ssize_t rc = SSL_read(handle->ssl, buf, sz);
  if (rc < 0) {
    int err = SSL_get_error(handle->ssl, rc);
    if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
      errno = EAGAIN;
    } else if (err != SSL_ERROR_SYSCALL || !errno)  {
      errno = EIO;
    }
    rc = -1;
  }
  return rc;
}

ssize_t SSLwrite(struct SSLHandle *handle, const char *buf, size_t sz) {
  errno = 0;
  ssize_t rc = SSL_write(handle->ssl, buf, sz);
  if (rc < 0) {
    int err = SSL_get_error(handle->ssl, rc);
    if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
      errno = EAGAIN;
    } else if (err != SSL_ERROR_SYSCALL || !errno) {
      errno = EIO;
    }
    rc = -1;
  }
  return rc;
}

#endif  // USE_AXTLS
